/*
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/*
 *    AttributeVisualizationPanel.java
 *    Copyright (C) 2003-2012 University of Waikato, Hamilton, New Zealand
 *
 */

package weka.gui;

import weka.core.Attribute;
import weka.core.AttributeStats;
import weka.core.Instances;
import weka.core.SparseInstance;
import weka.core.Utils;
import weka.gui.visualize.PrintableComponent;
import weka.gui.visualize.PrintablePanel;

import javax.swing.JComboBox;
import javax.swing.JFrame;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseEvent;
import java.io.FileReader;
import java.util.ArrayList;

/**
 * Creates a panel that shows a visualization of an attribute in a dataset. For
 * nominal attribute it shows a bar plot, with each bar corresponding to each
 * nominal value of the attribute with its height equal to the frequecy that
 * value appears in the dataset. For numeric attributes, it displays a
 * histogram. The width of an interval in the histogram is calculated using
 * Scott's(1979) method: <br>
 * intervalWidth = Max(1, 3.49*Std.Dev*numInstances^(1/3)) Then the number of
 * intervals is calculated by: <br>
 * intervals = max(1, Math.round(Range/intervalWidth);
 * 
 * @author Ashraf M. Kibriya (amk14@cs.waikato.ac.nz)
 * @version $Revision$
 */
public class AttributeVisualizationPanel extends PrintablePanel {

  /** for serialization */
  private static final long serialVersionUID = -8650490488825371193L;

  /** This holds the current set of instances */
  protected Instances m_data;

  /**
   * This holds the attribute stats of the current attribute on display. It is
   * calculated in setAttribute(int idx) when it is called to set a new
   * attribute index.
   */
  protected AttributeStats m_as;

  /** Cache of attribute stats info for the current data set */
  protected AttributeStats[] m_asCache;

  /**
   * This holds the index of the current attribute on display and should be set
   * through setAttribute(int idx).
   */
  protected int m_attribIndex;

  /**
   * This holds the max value of the current attribute. In case of nominal
   * attribute it is the highest count that a nominal value has in the attribute
   * (given by m_as.nominalWeights[i]), otherwise in case of numeric attribute
   * it is simply the maximum value present in the attribute (given by
   * m_as.numericStats.max). It is used to calculate the ratio of the height of
   * the bars with respect to the height of the display area.
   */
  protected double m_maxValue;

  /**
   * This array holds the count (or height) for the each of the bars in a
   * barplot or a histogram. In case of barplots (and current attribute being
   * nominal) its length (and the number of bars) is equal to the number of
   * nominal values in the current attribute, with each field of the array being
   * equal to the count of each nominal that it represents (the count of ith
   * nominal value of an attribute is given by m_as.nominalWeights[i]). Whereas,
   * in case of histograms (and current attribute being numeric) the width of
   * its intervals is calculated by Scott's(1979) method: <br>
   * intervalWidth = Max(1, 3.49*Std.Dev*numInstances^(1/3)) And the number of
   * intervals by: <br>
   * intervals = max(1, Math.round(Range/intervalWidth); Then each field of this
   * array contains the number of values of the current attribute that fall in
   * the histogram interval that it represents. <br>
   * NOTE: The values of this array are only calculated if the class attribute
   * is not set or if it is numeric.
   */
  protected double[] m_histBarCounts;

  /**
   * This array holds the per class count (or per class height) of the each of
   * the bars in a barplot or a histogram. For nominal attributes the format is: <br>
   * m_histBarClassCounts[nominalValue][classValue+1]. For numeric attributes
   * the format is: <br>
   * m_histBarClassCounts[interval][classValues+1], <br>
   * where the number of intervals is calculated by the Scott's method as
   * mentioned above. The array is initialized to have 1+numClasses to
   * accomodate for instances with missing class value. The ones with missing
   * class value are displayed as a black sub par in a histogram or a barplot.
   * 
   * NOTE: The values of this array are only calculated if the class attribute
   * is set and it is nominal.
   */
  SparseInstance m_histBarClassCounts[];

  /**
   * Contains the range of each bar in a histogram. It is used to work out the
   * range of bar the mouse pointer is on in getToolTipText().
   */
  protected double m_barRange;

  /** Contains the current class index. */
  protected int m_classIndex;

  /**
   * This stores the BarCalc or HistCalc thread while a new barplot or histogram
   * is being calculated.
   */
  private Thread m_hc;

  /** True if the thread m_hc above is running. */
  private boolean m_threadRun = false;

  private boolean m_doneCurrentAttribute = false;
  private boolean m_displayCurrentAttribute = false;

  /**
   * This stores and lets the user select a class attribute. It also has an
   * entry "No Class" if the user does not want to set a class attribute for
   * colouring.
   */
  protected JComboBox m_colorAttrib;

  /**
   * Fontmetrics used to get the font size which is required for calculating
   * displayable area size, bar height ratio and width of strings that are
   * displayed on top of bars indicating their count.
   */
  private final FontMetrics m_fm;

  /**
   * Lock variable to synchronize the different threads running currently in
   * this class. There are two to three threads in this class, AWT paint thread
   * which is handled differently in paintComponent() which checks on
   * m_threadRun to determine if it can perform full paint or not, the second
   * thread is the main execution thread and the third is the one represented by
   * m_hc which we start when we want to calculate the internal fields for a bar
   * plot or a histogram.
   */
  private final Integer m_locker = new Integer(1);

  // Image img;

  /**
   * Contains discrete colours for colouring of subbars of histograms and bar
   * plots when the class attribute is set and is nominal
   */
  private final ArrayList<Color> m_colorList = new ArrayList<Color>();

  /** default colour list */
  private static final Color[] m_defaultColors = { Color.blue, Color.red,
    Color.cyan, new Color(75, 123, 130), Color.pink, Color.green, Color.orange,
    new Color(255, 0, 255), new Color(255, 0, 0), new Color(0, 255, 0), };

  /**
   * Constructor - If used then the class will not show the class selection
   * combo box.
   */
  public AttributeVisualizationPanel() {
    this(false);
  }

  /**
   * Constructor.
   * 
   * @param showColouringOption - should be true if the class selection combo
   *          box is to be displayed with the histogram/barplot, or false
   *          otherwise. P.S: the combo box is always created it just won't be
   *          shown if showColouringOption is false.
   */
  public AttributeVisualizationPanel(boolean showColouringOption) {
    this.setFont(new Font("Default", Font.PLAIN, 9));
    m_fm = this.getFontMetrics(this.getFont());
    this.setToolTipText("");
    FlowLayout fl = new FlowLayout(FlowLayout.LEFT);
    this.setLayout(fl);
    this.addComponentListener(new ComponentAdapter() {
      @Override
      public void componentResized(ComponentEvent ce) {
        if (m_data != null) {
          // calcGraph();
        }
      }
    });

    m_colorAttrib = new JComboBox();
    m_colorAttrib.addItemListener(new ItemListener() {
      @Override
      public void itemStateChanged(ItemEvent ie) {
        if (ie.getStateChange() == ItemEvent.SELECTED) {
          m_classIndex = m_colorAttrib.getSelectedIndex() - 1;
          if (m_as != null) {
            setAttribute(m_attribIndex);
          }
        }
      }
    });

    if (showColouringOption) {
      // m_colorAttrib.setVisible(false);
      this.add(m_colorAttrib);
      validate();
    }
  }

  /**
   * Sets the instances for use
   * 
   * @param newins a set of Instances
   */
  public void setInstances(Instances newins) {

    m_attribIndex = 0;
    m_as = null;
    m_data = new Instances(newins);
    if (m_colorAttrib != null) {
      m_colorAttrib.removeAllItems();
      m_colorAttrib.addItem("No class");
      for (int i = 0; i < m_data.numAttributes(); i++) {
        String type = "(" + Attribute.typeToStringShort(m_data.attribute(i))
          + ")";
        m_colorAttrib.addItem(new String("Class: " + m_data.attribute(i).name()
          + " " + type));
      }
      if (m_data.classIndex() >= 0) {
        m_colorAttrib.setSelectedIndex(m_data.classIndex() + 1);
      } else {
        m_colorAttrib.setSelectedIndex(m_data.numAttributes());
      }
      // if (m_data.classIndex() >= 0) {
      // m_colorAttrib.setSelectedIndex(m_data.classIndex());
      // }
    }
    if (m_data.classIndex() >= 0) {
      m_classIndex = m_data.classIndex();
    } else {
      m_classIndex = m_data.numAttributes() - 1;
    }

    m_asCache = new AttributeStats[m_data.numAttributes()];
  }

  /**
   * Returns the class selection combo box if the parent component wants to
   * place it in itself or in some component other than this component.
   */
  public JComboBox getColorBox() {
    return m_colorAttrib;
  }

  /**
   * Get the coloring (class) index for the plot
   * 
   * @return an <code>int</code> value
   */
  public int getColoringIndex() {
    return m_classIndex; // m_colorAttrib.getSelectedIndex();
  }

  /**
   * Set the coloring (class) index for the plot
   * 
   * @param ci an <code>int</code> value
   */
  public void setColoringIndex(int ci) {
    m_classIndex = ci;
    if (m_colorAttrib != null) {
      m_colorAttrib.setSelectedIndex(ci + 1);
    } else {
      setAttribute(m_attribIndex);
    }
  }

  /**
   * Tells the panel which attribute to visualize.
   * 
   * @param index The index of the attribute
   */
  public void setAttribute(int index) {

    synchronized (m_locker) {
      // m_threadRun = true;
      m_threadRun = false;
      m_doneCurrentAttribute = false;
      m_displayCurrentAttribute = true;
      // if(m_hc!=null && m_hc.isAlive()) m_hc.stop();
      m_attribIndex = index;
      if (m_asCache[index] != null) {
        m_as = m_asCache[index];
      } else {
        m_asCache[index] = m_data.attributeStats(index);
        m_as = m_asCache[index];
      }
      // m_as = m_data.attributeStats(m_attribIndex);
      // m_classIndex = m_colorAttrib.getSelectedIndex();
    }

    this.repaint();
    // calcGraph();
  }

  /**
   * Recalculates the barplot or histogram to display, required usually when the
   * attribute is changed or the component is resized.
   */
  public void calcGraph(int panelWidth, int panelHeight) {

    synchronized (m_locker) {
      m_threadRun = true;
      if (m_as.nominalWeights != null) {
        m_hc = new BarCalc(panelWidth, panelHeight);
        m_hc.setPriority(Thread.MIN_PRIORITY);
        m_hc.start();
      } else if (m_as.numericStats != null) {
        m_hc = new HistCalc();
        m_hc.setPriority(Thread.MIN_PRIORITY);
        m_hc.start();
      } else {
        m_histBarCounts = null;
        m_histBarClassCounts = null;
        m_doneCurrentAttribute = true;
        m_threadRun = false;
        this.repaint();
      }
    }
  }

  /**
   * Internal class that calculates the barplot to display, in a separate
   * thread. In particular it initializes some of the crucial internal fields
   * required by paintComponent() to display the histogram for the current
   * attribute. These include: m_histBarCounts or m_histBarClassCounts,
   * m_maxValue and m_colorList.
   */
  private class BarCalc extends Thread {
    private final int m_panelWidth;

    public BarCalc(int panelWidth, int panelHeight) {
      m_panelWidth = panelWidth;
    }

    @Override
    public void run() {
      synchronized (m_locker) {
        // there is no use doing/displaying anything if the resolution
        // of the panel is less than the number of values for this attribute
        if (m_data.attribute(m_attribIndex).numValues() > m_panelWidth) {
          m_histBarClassCounts = null;
          m_threadRun = false;
          m_doneCurrentAttribute = true;
          m_displayCurrentAttribute = false;
          AttributeVisualizationPanel.this.repaint();
          return;
        }

        if ((m_classIndex >= 0) && (m_data.attribute(m_classIndex).isNominal())) {
          SparseInstance histClassCounts[];
          histClassCounts = new SparseInstance[m_data.attribute(m_attribIndex)
            .numValues()];
          // [m_data.attribute(m_classIndex).numValues()+1];

          if (m_as.nominalWeights.length > 0) {
            m_maxValue = m_as.nominalWeights[0];
            for (int i = 0; i < m_data.attribute(m_attribIndex).numValues(); i++) {
              if (m_as.nominalWeights[i] > m_maxValue) {
                m_maxValue = m_as.nominalWeights[i];
              }
            }
          } else {
            m_maxValue = 0;
          }

          if (m_colorList.size() == 0) {
            m_colorList.add(Color.black);
          }
          for (int i = m_colorList.size(); i < m_data.attribute(m_classIndex)
            .numValues() + 1; i++) {
            Color pc = m_defaultColors[(i - 1) % 10];
            int ija = (i - 1) / 10;
            ija *= 2;

            for (int j = 0; j < ija; j++) {
              pc = pc.darker();
            }

            m_colorList.add(pc);
          }

          // first sort data on attribute values
          m_data.sort(m_attribIndex);
          double[] tempClassCounts = null;
          int tempAttValueIndex = -1;

          for (int k = 0; k < m_data.numInstances(); k++) {
            // System.out.println("attrib: "+
            // m_data.instance(k).value(m_attribIndex)+
            // " class: "+
            // m_data.instance(k).value(m_classIndex));
            if (!m_data.instance(k).isMissing(m_attribIndex)) {
              // check to see if we need to allocate some space here
              if (m_data.instance(k).value(m_attribIndex) != tempAttValueIndex) {
                if (tempClassCounts != null) {
                  // set up the sparse instance for the previous bar (if any)
                  int numNonZero = 0;
                  for (double tempClassCount : tempClassCounts) {
                    if (tempClassCount > 0) {
                      numNonZero++;
                    }
                  }
                  double[] nonZeroVals = new double[numNonZero];
                  int[] nonZeroIndices = new int[numNonZero];
                  int count = 0;
                  for (int z = 0; z < tempClassCounts.length; z++) {
                    if (tempClassCounts[z] > 0) {
                      nonZeroVals[count] = tempClassCounts[z];
                      nonZeroIndices[count++] = z;
                    }
                  }
                  SparseInstance tempS = new SparseInstance(1.0, nonZeroVals,
                    nonZeroIndices, tempClassCounts.length);
                  histClassCounts[tempAttValueIndex] = tempS;
                }

                tempClassCounts = new double[m_data.attribute(m_classIndex)
                  .numValues() + 1];
                tempAttValueIndex = (int) m_data.instance(k).value(
                  m_attribIndex);

                /*
                 * histClassCounts[(int)m_data.instance(k).value(m_attribIndex)]
                 * = new double[m_data.attribute(m_classIndex).numValues()+1];
                 */
              }
              if (m_data.instance(k).isMissing(m_classIndex)) {
                /*
                 * histClassCounts[(int)m_data.instance(k).value(m_attribIndex)]
                 * [0] += m_data.instance(k).weight();
                 */
                tempClassCounts[0] += m_data.instance(k).weight();
              } else {
                tempClassCounts[(int) m_data.instance(k).value(m_classIndex) + 1] += m_data
                  .instance(k).weight();

                /*
                 * histClassCounts[(int)m_data.instance(k).value(m_attribIndex)]
                 * [(int)m_data.instance(k).value(m_classIndex)+1] +=
                 * m_data.instance(k).weight();
                 */
              }
            }
          }

          // set up sparse instance for last bar?
          if (tempClassCounts != null) {
            // set up the sparse instance for the previous bar (if any)
            int numNonZero = 0;
            for (double tempClassCount : tempClassCounts) {
              if (tempClassCount > 0) {
                numNonZero++;
              }
            }
            double[] nonZeroVals = new double[numNonZero];
            int[] nonZeroIndices = new int[numNonZero];
            int count = 0;
            for (int z = 0; z < tempClassCounts.length; z++) {
              if (tempClassCounts[z] > 0) {
                nonZeroVals[count] = tempClassCounts[z];
                nonZeroIndices[count++] = z;
              }
            }
            SparseInstance tempS = new SparseInstance(1.0, nonZeroVals,
              nonZeroIndices, tempClassCounts.length);
            histClassCounts[tempAttValueIndex] = tempS;
          }

          // for(int i=0; i<histClassCounts.length; i++) {
          // int sum=0;
          // for(int j=0; j<histClassCounts[i].length; j++) {
          // sum = sum+histClassCounts[i][j];
          // }
          // System.out.println("histCount: "+sum+" Actual: "+
          // m_as.nominalWeights[i]);
          // }

          m_threadRun = false;
          m_doneCurrentAttribute = true;
          m_displayCurrentAttribute = true;
          m_histBarClassCounts = histClassCounts;
          // Image tmpImg = new BufferedImage(getWidth(), getHeight(),
          // BufferedImage.TYPE_INT_RGB);
          // drawGraph( tmpImg.getGraphics() );
          // img = tmpImg;
          AttributeVisualizationPanel.this.repaint();
        } else {
          double histCounts[];
          histCounts = new double[m_data.attribute(m_attribIndex).numValues()];

          if (m_as.nominalWeights.length > 0) {
            m_maxValue = m_as.nominalWeights[0];
            for (int i = 0; i < m_data.attribute(m_attribIndex).numValues(); i++) {
              if (m_as.nominalWeights[i] > m_maxValue) {
                m_maxValue = m_as.nominalWeights[i];
              }
            }
          } else {
            m_maxValue = 0;
          }

          for (int k = 0; k < m_data.numInstances(); k++) {
            if (!m_data.instance(k).isMissing(m_attribIndex)) {
              histCounts[(int) m_data.instance(k).value(m_attribIndex)] += m_data
                .instance(k).weight();
            }
          }
          m_threadRun = false;
          m_displayCurrentAttribute = true;
          m_doneCurrentAttribute = true;
          m_histBarCounts = histCounts;
          // Image tmpImg = new BufferedImage(getWidth(), getHeight(),
          // BufferedImage.TYPE_INT_RGB);
          // drawGraph( tmpImg.getGraphics() );
          // img = tmpImg;
          AttributeVisualizationPanel.this.repaint();
        }
      } // end synchronized
    } // end run()
  }

  /**
   * Internal class that calculates the histogram to display, in a separate
   * thread. In particular it initializes some of the crucial internal fields
   * required by paintComponent() to display the histogram for the current
   * attribute. These include: m_histBarCounts or m_histBarClassCounts,
   * m_maxValue and m_colorList.
   */
  private class HistCalc extends Thread {
    @Override
    public void run() {
      synchronized (m_locker) {
        if ((m_classIndex >= 0) && (m_data.attribute(m_classIndex).isNominal())) {

          int intervals;
          double intervalWidth = 0.0;

          // This uses the M.P.Wand's method to calculate the histogram's
          // interval width. See "Data-Based Choice of Histogram Bin Width", in
          // The American Statistician, Vol. 51, No. 1, Feb., 1997, pp. 59-64.
          // intervalWidth = Math.pow(6D/( -psi(2,
          // g21())*m_data.numInstances()),
          // 1/3D );

          // This uses the Scott's method to calculate the histogram's interval
          // width. See "On optimal and data-based histograms".
          // See Biometrika, 66, 605-610 OR see the same paper mentioned above.
          intervalWidth = 3.49 * m_as.numericStats.stdDev
            * Math.pow(m_data.numInstances(), -1 / 3D);
          // The Math.max is introduced to remove the possibility of
          // intervals=0 and =NAN that can happen if respectively all the
          // numeric
          // values are the same or the interval width is evaluated to zero.
          intervals = Math.max(
            1,
            (int) Math.round((m_as.numericStats.max - m_as.numericStats.min)
              / intervalWidth));

          // System.out.println("Max: "+m_as.numericStats.max+
          // " Min: "+m_as.numericStats.min+
          // " stdDev: "+m_as.numericStats.stdDev+
          // "intervalWidth: "+intervalWidth);

          // The number 4 below actually represents a padding of 3 pixels on
          // each side of the histogram, and is also reflected in other parts of
          // the code in the shape of numerical constants like "6" here.
          if (intervals > AttributeVisualizationPanel.this.getWidth()) {
            intervals = AttributeVisualizationPanel.this.getWidth() - 6;
            if (intervals < 1) {
              intervals = 1;
            }
          }
          double histClassCounts[][] = new double[intervals][m_data.attribute(
            m_classIndex).numValues() + 1];

          double barRange = (m_as.numericStats.max - m_as.numericStats.min)
            / histClassCounts.length;

          m_maxValue = 0;

          if (m_colorList.size() == 0) {
            m_colorList.add(Color.black);
          }
          for (int i = m_colorList.size(); i < m_data.attribute(m_classIndex)
            .numValues() + 1; i++) {
            Color pc = m_defaultColors[(i - 1) % 10];
            int ija = (i - 1) / 10;
            ija *= 2;
            for (int j = 0; j < ija; j++) {
              pc = pc.darker();
            }
            m_colorList.add(pc);
          }

          for (int k = 0; k < m_data.numInstances(); k++) {
            int t = 0; // This holds the interval that the attibute value of the
                       // new instance belongs to.
            try {
              if (!m_data.instance(k).isMissing(m_attribIndex)) {
                // 1. see footnote at the end of this file
                t = (int) Math.ceil((float) ((m_data.instance(k).value(
                  m_attribIndex) - m_as.numericStats.min) / barRange));
                if (t == 0) {
                  if (m_data.instance(k).isMissing(m_classIndex)) {
                    histClassCounts[t][0] += m_data.instance(k).weight();
                  } else {
                    histClassCounts[t][(int) m_data.instance(k).value(
                      m_classIndex) + 1] += m_data.instance(k).weight();
                    // if(histCounts[t]>m_maxValue)
                    // m_maxValue = histCounts[t];
                  }
                } else {
                  if (m_data.instance(k).isMissing(m_classIndex)) {
                    histClassCounts[t - 1][0] += m_data.instance(k).weight();
                  } else {
                    histClassCounts[t - 1][(int) m_data.instance(k).value(
                      m_classIndex) + 1] += m_data.instance(k).weight();
                    // if(histCounts[t-1]>m_maxValue)
                    // m_maxValue = histCounts[t-1];
                  }
                }
              }
            } catch (ArrayIndexOutOfBoundsException ae) {
              System.out
                .println("t:"
                  + (t)
                  + " barRange:"
                  + barRange
                  + " histLength:"
                  + histClassCounts.length
                  + " value:"
                  + m_data.instance(k).value(m_attribIndex)
                  + " min:"
                  + m_as.numericStats.min
                  + " sumResult:"
                  + (m_data.instance(k).value(m_attribIndex) - m_as.numericStats.min)
                  + " divideResult:"
                  + (float) ((m_data.instance(k).value(m_attribIndex) - m_as.numericStats.min) / barRange)
                  + " finalResult:"
                  + Math
                    .ceil((float) ((m_data.instance(k).value(m_attribIndex) - m_as.numericStats.min) / barRange)));
            }
          }
          for (double[] histClassCount : histClassCounts) {
            double sum = 0;
            for (double element : histClassCount) {
              sum = sum + element;
            }
            if (m_maxValue < sum) {
              m_maxValue = sum;
            }
          }

          // convert to sparse instances
          SparseInstance[] histClassCountsSparse = new SparseInstance[histClassCounts.length];

          for (int i = 0; i < histClassCounts.length; i++) {
            int numSparseValues = 0;
            for (int j = 0; j < histClassCounts[i].length; j++) {
              if (histClassCounts[i][j] > 0) {
                numSparseValues++;
              }
            }
            double[] sparseValues = new double[numSparseValues];
            int[] sparseIndices = new int[numSparseValues];
            int count = 0;
            for (int j = 0; j < histClassCounts[i].length; j++) {
              if (histClassCounts[i][j] > 0) {
                sparseValues[count] = histClassCounts[i][j];
                sparseIndices[count++] = j;
              }
            }

            SparseInstance tempS = new SparseInstance(1.0, sparseValues,
              sparseIndices, histClassCounts[i].length);
            histClassCountsSparse[i] = tempS;

          }

          m_histBarClassCounts = histClassCountsSparse;
          m_barRange = barRange;

        } else { // else if the class attribute is numeric or the class is not
                 // set

          int intervals;
          double intervalWidth;
          // At the time of this coding the
          // possibility of datasets with zero instances
          // was being dealt with in the
          // PreProcessPanel of weka Explorer.

          // old method of calculating number of intervals
          // intervals = m_as.totalCount>10 ?
          // (int)(m_as.totalCount*0.1):(int)m_as.totalCount;

          // This uses the M.P.Wand's method to calculate the histogram's
          // interval width. See "Data-Based Choice of Histogram Bin Width", in
          // The American Statistician, Vol. 51, No. 1, Feb., 1997, pp. 59-64.
          // intervalWidth = Math.pow(6D/(-psi(2, g21())*m_data.numInstances()
          // ),
          // 1/3D );

          // This uses the Scott's method to calculate the histogram's interval
          // width. See "On optimal and data-based histograms".
          // See Biometrika, 66, 605-610 OR see the same paper mentioned above.
          intervalWidth = 3.49 * m_as.numericStats.stdDev
            * Math.pow(m_data.numInstances(), -1 / 3D);
          // The Math.max is introduced to remove the possibility of
          // intervals=0 and =NAN that can happen if respectively all the
          // numeric
          // values are the same or the interval width is evaluated to zero.
          intervals = Math.max(
            1,
            (int) Math.round((m_as.numericStats.max - m_as.numericStats.min)
              / intervalWidth));

          // The number 4 below actually represents a padding of 3 pixels on
          // each side of the histogram, and is also reflected in other parts of
          // the code in the shape of numerical constants like "6" here.
          if (intervals > AttributeVisualizationPanel.this.getWidth()) {
            intervals = AttributeVisualizationPanel.this.getWidth() - 6;
            if (intervals < 1) {
              intervals = 1;
            }
          }

          double[] histCounts = new double[intervals];
          double barRange = (m_as.numericStats.max - m_as.numericStats.min)
            / histCounts.length;

          m_maxValue = 0;

          for (int k = 0; k < m_data.numInstances(); k++) {
            int t = 0; // This holds the interval to which the current
                       // attribute's
                       // value of this particular instance k belongs to.

            if (m_data.instance(k).isMissing(m_attribIndex)) {
              continue; // ignore missing values
            }

            try {
              // 1. see footnote at the end of this file
              t = (int) Math.ceil((float) ((m_data.instance(k).value(
                m_attribIndex) - m_as.numericStats.min) / barRange));
              if (t == 0) {
                histCounts[t] += m_data.instance(k).weight();
                if (histCounts[t] > m_maxValue) {
                  m_maxValue = histCounts[t];
                }
              } else {
                histCounts[t - 1] += m_data.instance(k).weight();
                if (histCounts[t - 1] > m_maxValue) {
                  m_maxValue = histCounts[t - 1];
                }
              }
            } catch (ArrayIndexOutOfBoundsException ae) {
              ae.printStackTrace();
              System.out
                .println("t:"
                  + (t)
                  + " barRange:"
                  + barRange
                  + " histLength:"
                  + histCounts.length
                  + " value:"
                  + m_data.instance(k).value(m_attribIndex)
                  + " min:"
                  + m_as.numericStats.min
                  + " sumResult:"
                  + (m_data.instance(k).value(m_attribIndex) - m_as.numericStats.min)
                  + " divideResult:"
                  + (float) ((m_data.instance(k).value(m_attribIndex) - m_as.numericStats.min) / barRange)
                  + " finalResult:"
                  + Math
                    .ceil((float) ((m_data.instance(k).value(m_attribIndex) - m_as.numericStats.min) / barRange)));
            }
          }
          m_histBarCounts = histCounts;
          m_barRange = barRange;
        }

        m_threadRun = false;
        m_displayCurrentAttribute = true;
        m_doneCurrentAttribute = true;
        // Image tmpImg = new BufferedImage(getWidth(), getHeight(),
        // BufferedImage.TYPE_INT_RGB);
        // drawGraph( tmpImg.getGraphics() );
        // img = tmpImg;
        AttributeVisualizationPanel.this.repaint();
      }
    }

    /****
     * Code for M.P.Wand's method of histogram bin width selection. There is
     * some problem with it. It always comes up -ve value which is raised to the
     * power 1/3 and gives an NAN. private static final int M=400; private
     * double psi(int r, double g) { double val;
     * 
     * double sum=0.0; for(int i=0; i<M; i++) { double valCjKj=0.0; for(int j=0;
     * j<M; j++) { valCjKj += c(j) * k(r, j-i, g); } sum += valCjKj*c(i); }
     * 
     * val = Math.pow(m_data.numInstances(), -2) * sum;
     * //System.out.println("psi returns: "+val); return val; } private double
     * g21() { double val;
     * 
     * val = Math.pow(2 / ( Math.sqrt(2D*Math.PI)*psi(4, g22()) *
     * m_data.numInstances() ), 1/5D) * Math.sqrt(2) * m_as.numericStats.stdDev;
     * //System.out.println("g21 returns: "+val); return val; } private double
     * g22() { double val;
     * 
     * val = Math.pow( 2D/(5*m_data.numInstances()), 1/7D) * Math.sqrt(2) *
     * m_as.numericStats.stdDev; //System.out.println("g22 returns: "+val);
     * return val; } private double c(int j) { double val=0.0; double sigma =
     * (m_as.numericStats.max - m_as.numericStats.min)/(M-1);
     * 
     * //System.out.println("In c before doing the sum we have");
     * //System.out.println("max: " +m_as.numericStats.max+" min: "+ //
     * m_as.numericStats.min+" sigma: "+sigma);
     * 
     * for(int i=0; i<m_data.numInstances(); i++) {
     * if(!m_data.instance(i).isMissing(m_attribIndex)) val += Math.max( 0, ( 1
     * - Math.abs( Math.pow(sigma, -1)*(m_data.instance(i).value(m_attribIndex)
     * - j) ) ) ); } //System.out.println("c returns: "+val); return val; }
     * private double k(int r, int j, double g) { double val; double sigma =
     * (m_as.numericStats.max - m_as.numericStats.min)/(M-1);
     * //System.out.println("Before calling L we have");
     * //System.out.println("Max: "
     * +m_as.numericStats.max+" Min: "+m_as.numericStats.min+"\n"+ //
     * "r: "+r+" j: "+j+" g: "+g); val = Math.pow( g, -r-1) * L(sigma*j/g);
     * //System.out.println("k returns: "+val); return val; } private double
     * L(double x) { double val;
     * 
     * val = Math.pow( 2*Math.PI, -1/2D ) * Math.exp( -(x*x)/2D );
     * //System.out.println("L returns: "+val); return val; } End of Wand's
     * method
     */
  }

  /**
   * Returns "&lt;nominal value&gt; [&lt;nominal value count&gt;]" if displaying
   * a bar plot and mouse is on some bar. If displaying histogram then it <li>
   * returns "count &lt;br&gt; [&lt;bars Range&gt;]" if mouse is on the first
   * bar.</li> <li>returns "count &lt;br&gt; (&lt;bar's Range&gt;]" if mouse is
   * on some bar other than the first one.</li> Otherwise it returns ""
   * 
   * @param ev The mouse event
   */
  @Override
  public String getToolTipText(MouseEvent ev) {

    if (m_as != null && m_as.nominalWeights != null) { // if current attrib is
                                                       // nominal

      float intervalWidth = this.getWidth()
        / (float) m_as.nominalWeights.length;
      double heightRatio;
      int barWidth, x = 0;

      // if intervalWidth is at least six then bar width is 80% of intervalwidth
      if (intervalWidth > 5) {
        barWidth = (int) Math.floor(intervalWidth * 0.8F);
      } else {
        barWidth = 1; // Otherwise barwidth is 1 & padding would be at least 1.
      }

      // initializing x to maximum of 1 or 10% of interval width (i.e. half of
      // the padding which is 20% of interval width, as there is 10% on each
      // side of the bar) so that it points to the start of the 1st bar
      x = x
        + (int) ((Math.floor(intervalWidth * 0.1F)) < 1 ? 1 : (Math
          .floor(intervalWidth * 0.1F)));

      // Adding to x the appropriate value so that it points to the 1st bar of
      // our "centered" barplot. If subtracting barplots width from panel width
      // gives <=2 then the barplot is not centered.
      if (this.getWidth()
        - (m_as.nominalWeights.length * barWidth + (int) ((Math
          .floor(intervalWidth * 0.2F)) < 1 ? 1 : (Math
          .floor(intervalWidth * 0.2F)))
          * m_as.nominalWeights.length) > 2) {

        // The following amounts to adding to x the half of the area left after
        // subtracting from the components width the width of the whole barplot
        // (i.e. width of all the bars plus the width of all the bar paddings,
        // thereby equaling to the whole barplot), since our barplot is
        // centered.
        x += (this.getWidth() - (m_as.nominalWeights.length * barWidth + (int) ((Math
          .floor(intervalWidth * 0.2F)) < 1 ? 1 : (Math
          .floor(intervalWidth * 0.2F)))
          * m_as.nominalWeights.length)) / 2;
      }

      for (int i = 0; i < m_as.nominalWeights.length; i++) {
        heightRatio = (this.getHeight() - (double) m_fm.getHeight())
          / m_maxValue;

        // if our mouse is on a bar then return the count of this bar in our
        // barplot
        if (ev.getX() >= x
          && ev.getX() <= x + barWidth
          && ev.getY() >= this.getHeight()
            - Math.round(m_as.nominalWeights[i] * heightRatio)) {
          return (m_data.attribute(m_attribIndex).value(i) + " ["
            + Utils.doubleToString(m_as.nominalWeights[i], 3) + "]");
        }
        // otherwise advance x to next bar and check that. Add barwidth to x
        // and padding which is max(1, 20% of interval width)
        x = x
          + barWidth
          + (int) ((Math.floor(intervalWidth * 0.2F)) < 1 ? 1 : (Math
            .floor(intervalWidth * 0.2F)));
      }
    } else if (m_threadRun == false && // if attrib is numeric
      (m_histBarCounts != null || m_histBarClassCounts != null)) {

      int x = 0, barWidth;
      double bar = m_as.numericStats.min;

      // if the class attribute is set and it is nominal
      if ((m_classIndex >= 0) && (m_data.attribute(m_classIndex).isNominal())) {
        // there is 3 pixels of padding on each side of the histogram
        // the barwidth is 1 if after removing the padding its width is less
        // then the displayable width
        barWidth = ((this.getWidth() - 6) / m_histBarClassCounts.length) < 1 ? 1
          : ((this.getWidth() - 6) / m_histBarClassCounts.length);

        // initializing x to 3 adding appropriate value to make it point to the
        // start of the 1st bar of our "centered" histogram.
        x = 3;
        if ((this.getWidth() - (x + m_histBarClassCounts.length * barWidth)) > 5) {
          x += (this.getWidth() - (x + m_histBarClassCounts.length * barWidth)) / 2;
        }

        if (ev.getX() - x >= 0) {
          // The temp holds the index of the current interval that we are
          // looking
          // at
          int temp = (int) ((ev.getX() - x) / (barWidth + 0.0000000001));
          if (temp == 0) { // handle the special case temp==0. see footnote 1
            double sum = 0;
            for (int k = 0; k < m_histBarClassCounts[0].numValues(); k++) {
              sum += m_histBarClassCounts[0].valueSparse(k);
            }
            // return the count of the interval mouse is pointing to plus
            // the range of values that fall into this interval
            return ("<html><center><font face=Dialog size=-1>"
              + Utils.doubleToString(sum, 3) + "<br>" + "["
              + Utils.doubleToString(bar + m_barRange * temp, 3) + ", "
              + Utils.doubleToString((bar + m_barRange * (temp + 1)), 3) + "]" + "</font></center></html>");
          } else if (temp < m_histBarClassCounts.length) { // handle case
                                                           // temp!=0
            double sum = 0;
            for (int k = 0; k < m_histBarClassCounts[temp].numValues(); k++) {
              sum += m_histBarClassCounts[temp].valueSparse(k);
            }
            // return the count of the interval mouse is pointing to plus
            // the range of values that fall into this interval
            return ("<html><center><font face=Dialog size=-1>"
              + Utils.doubleToString(sum, 3) + "<br>("
              + Utils.doubleToString(bar + m_barRange * temp, 3) + ", "
              + Utils.doubleToString((bar + m_barRange * (temp + 1)), 3) + "]</font></center></html>");
          }
        }
      } else { // else if the class attribute is not set or is numeric
        barWidth = ((this.getWidth() - 6) / m_histBarCounts.length) < 1 ? 1
          : ((this.getWidth() - 6) / m_histBarCounts.length);

        // initializing x to 3 adding appropriate value to make it point to the
        // start of the 1st bar of our "centered" histogram.
        x = 3;
        if ((this.getWidth() - (x + m_histBarCounts.length * barWidth)) > 5) {
          x += (this.getWidth() - (x + m_histBarCounts.length * barWidth)) / 2;
        }

        if (ev.getX() - x >= 0) {
          // Temp holds the index of the current bar we are looking at.
          int temp = (int) ((ev.getX() - x) / (barWidth + 0.0000000001));

          // return interval count as well as its range
          if (temp == 0) {
            return ("<html><center><font face=Dialog size=-1>"
              + m_histBarCounts[0] + "<br>" + "["
              + Utils.doubleToString(bar + m_barRange * temp, 3) + ", "
              + Utils.doubleToString((bar + m_barRange * (temp + 1)), 3) + "]" + "</font></center></html>");
          } else if (temp < m_histBarCounts.length) {
            return ("<html><center><font face=Dialog size=-1>"
              + m_histBarCounts[temp] + "<br>" + "("
              + Utils.doubleToString(bar + m_barRange * temp, 3) + ", "
              + Utils.doubleToString((bar + m_barRange * (temp + 1)), 3) + "]" + "</font></center></html>");
          }
        }
      }
    }
    return PrintableComponent.getToolTipText(m_Printer);
  }

  /**
   * Paints this component
   * 
   * @param g The graphics object for this component
   */
  @Override
  public void paintComponent(Graphics g) {
    g.setColor(Color.WHITE);
    g.fillRect(0, 0, this.getWidth(), this.getHeight());
    g.setColor(Color.BLACK);

    if (m_as != null) { // If calculations have been done and histogram/barplot
      if (!m_doneCurrentAttribute && !m_threadRun) {
        calcGraph(this.getWidth(), this.getHeight());
      }
      if (m_threadRun == false && m_displayCurrentAttribute) { // calculation
                                                               // thread is not
                                                               // running
        int buttonHeight = 0;

        if (m_colorAttrib != null) {
          buttonHeight = m_colorAttrib.getHeight()
            + m_colorAttrib.getLocation().y;
        }

        // if current attribute is nominal then draw barplot.
        if (m_as.nominalWeights != null
          && (m_histBarClassCounts != null || m_histBarCounts != null)) {
          double heightRatio, intervalWidth;
          int x = 0, y = 0, barWidth;

          // if the class attribute is set and is nominal then draw coloured
          // subbars for each bar
          if ((m_classIndex >= 0)
            && (m_data.attribute(m_classIndex).isNominal())) {

            intervalWidth = (this.getWidth() / (float) m_histBarClassCounts.length);

            // Barwidth is 80% of interval width.The remaining 20% is padding,
            // 10% on each side of the bar. If interval width is less then 5 the
            // 20% of that value is less than 1, in that case we use bar width
            // of
            // 1 and padding of 1 pixel on each side of the bar.
            if (intervalWidth > 5) {
              barWidth = (int) Math.floor(intervalWidth * 0.8F);
            } else {
              barWidth = 1;
            }

            // initializing x to 10% of interval width or to 1 if 10% is <1.
            // This
            // is essentially the LHS padding of the 1st bar.
            x = x
              + (int) ((Math.floor(intervalWidth * 0.1F)) < 1 ? 1 : (Math
                .floor(intervalWidth * 0.1F)));

            // Add appropriate value to x so that it starts at the 1st bar of
            // a "centered" barplot.
            if (this.getWidth()
              - (m_histBarClassCounts.length * barWidth + (int) ((Math
                .floor(intervalWidth * 0.2F)) < 1 ? 1 : (Math
                .floor(intervalWidth * 0.2F)))
                * m_histBarClassCounts.length) > 2) {
              // We take the width of all the bars and all the paddings (20%
              // of interval width), and subtract it from the width of the panel
              // to get the extra space that would be left after drawing. We
              // divide that space by 2 to get its mid-point and add that to our
              // x, thus making the whole bar plot drawn centered in our
              // component.
              x += (this.getWidth() - (m_histBarClassCounts.length * barWidth + (int) ((Math
                .floor(intervalWidth * 0.2F)) < 1 ? 1 : (Math
                .floor(intervalWidth * 0.2F)))
                * m_histBarClassCounts.length)) / 2;
            }

            // this holds the count of the bar and will be calculated by adding
            // up the counts of individual subbars. It is displayed at the top
            // of each bar.
            double sum = 0;
            for (SparseInstance m_histBarClassCount : m_histBarClassCounts) {

              // calculating the proportion of the components height compared to
              // the maxvalue in our attribute, also taking into account the
              // height of font to display bars count and the height of the
              // class
              // ComboBox.
              heightRatio = (this.getHeight() - (double) m_fm.getHeight() - buttonHeight)
                / m_maxValue;
              y = this.getHeight();
              if (m_histBarClassCount != null) {
                for (int j = 0; j < m_histBarClassCount.numAttributes(); j++) {
                  sum = sum + m_histBarClassCount.value(j);
                  y = (int) (y - Math.round(m_histBarClassCount.value(j)
                    * heightRatio));
                  // selecting the colour corresponding to the current class.
                  g.setColor(m_colorList.get(j));
                  g.fillRect(x, y, barWidth, (int) Math
                    .round(m_histBarClassCount.value(j) * heightRatio));
                  g.setColor(Color.black);
                }
              }
              // drawing the bar count at the top of the bar if it is less than
              // interval width. draw it 1px up to avoid touching the bar.
              if (m_fm.stringWidth(Utils.doubleToString(sum, 1)) < intervalWidth) {
                g.drawString(Utils.doubleToString(sum, 1), x, y - 1);
              }
              // advancing x to the next bar by adding bar width and padding
              // of both the bars (i.e. RHS padding of the bar just drawn and
              // LHS
              // padding of the new bar).
              x = x
                + barWidth
                + (int) ((Math.floor(intervalWidth * 0.2F)) < 1 ? 1 : (Math
                  .floor(intervalWidth * 0.2F)));
              // reseting sum for the next bar.
              sum = 0;

            }
          }
          // else if class attribute is numeric or not set then draw black bars.
          else {
            intervalWidth = (this.getWidth() / (float) m_histBarCounts.length);

            // same as in the case of nominal class (see inside of if stmt
            // corresponding to the current else above).
            if (intervalWidth > 5) {
              barWidth = (int) Math.floor(intervalWidth * 0.8F);
            } else {
              barWidth = 1;
            }

            // same as in the case of nominal class (see inside of if stmt
            // corresponding to the current else above).
            x = x
              + (int) ((Math.floor(intervalWidth * 0.1F)) < 1 ? 1 : (Math
                .floor(intervalWidth * 0.1F)));

            // same as in the case of nominal class
            if (this.getWidth()
              - (m_histBarCounts.length * barWidth + (int) ((Math
                .floor(intervalWidth * 0.2F)) < 1 ? 1 : (Math
                .floor(intervalWidth * 0.2F)))
                * m_histBarCounts.length) > 2) {
              x += (this.getWidth() - (m_histBarCounts.length * barWidth + (int) ((Math
                .floor(intervalWidth * 0.2F)) < 1 ? 1 : (Math
                .floor(intervalWidth * 0.2F)))
                * m_histBarCounts.length)) / 2;
            }

            for (double m_histBarCount : m_histBarCounts) {
              // calculating the proportion of the height of the component
              // compared to the maxValue in our attribute.
              heightRatio = (this.getHeight() - (float) m_fm.getHeight() - buttonHeight)
                / m_maxValue;
              y = (int) (this.getHeight() - Math.round(m_histBarCount
                * heightRatio));
              g.fillRect(x, y, barWidth,
                (int) Math.round(m_histBarCount * heightRatio));
              // draw the bar count if it's width is smaller than intervalWidth.
              // draw it 1px above to avoid touching the bar.
              if (m_fm.stringWidth(Utils.doubleToString(m_histBarCount, 1)) < intervalWidth) {
                g.drawString(Utils.doubleToString(m_histBarCount, 1), x, y - 1);
              }
              // Advance x to the next bar by adding bar-width and padding
              // of the bars (RHS padding of current bar & LHS padding of next
              // bar).
              x = x
                + barWidth
                + (int) ((Math.floor(intervalWidth * 0.2F)) < 1 ? 1 : (Math
                  .floor(intervalWidth * 0.2F)));
            }
          }

        } // <--end if m_as.nominalCount!=null
        // if the current attribute is numeric then draw a histogram.
        else if (m_as.numericStats != null
          && (m_histBarClassCounts != null || m_histBarCounts != null)) {

          double heightRatio;
          int x = 0, y = 0, barWidth;

          // If the class attribute is set and is not numeric then draw coloured
          // subbars for the histogram bars
          if ((m_classIndex >= 0)
            && (m_data.attribute(m_classIndex).isNominal())) {

            // There is a padding of 3px on each side of the histogram.
            barWidth = ((this.getWidth() - 6) / m_histBarClassCounts.length) < 1 ? 1
              : ((this.getWidth() - 6) / m_histBarClassCounts.length);

            // initializing x to start at the start of the 1st bar after
            // padding.
            x = 3;
            // Adding appropriate value to x to account for a "centered"
            // histogram
            if ((this.getWidth() - (x + m_histBarClassCounts.length * barWidth)) > 5) {
              // We take the current value of x (histogram's RHS padding) and
              // add
              // the barWidths of all the bars to it to us the size of
              // our histogram. We subtract that from the width of the panel
              // giving us the extra space that would be left if the histogram
              // is
              // drawn and divide that by 2 to get the midpoint of that extra
              // space. That space is then added to our x, hence making the
              // histogram centered.
              x += (this.getWidth() - (x + m_histBarClassCounts.length
                * barWidth)) / 2;
            }

            for (SparseInstance m_histBarClassCount : m_histBarClassCounts) {
              if (m_histBarClassCount != null) {
                // Calculating height ratio. Leave space of 19 for an axis line
                // at
                // the bottom
                heightRatio = (this.getHeight() - (float) m_fm.getHeight()
                  - buttonHeight - 19)
                  / m_maxValue;
                y = this.getHeight() - 19;
                // This would hold the count of the bar (sum of sub-bars).
                double sum = 0;
                for (int j = 0; j < m_histBarClassCount.numValues(); j++) {
                  y = (int) (y - Math.round(m_histBarClassCount.valueSparse(j)
                    * heightRatio));
                  // System.out.println("Filling x:"+x+" y:"+y+" width:"+barWidth+
                  // " height:"+
                  // (m_histBarClassCounts[i][j]*heightRatio));
                  // selecting the color corresponding to our class
                  g.setColor(m_colorList.get(m_histBarClassCount.index(j)));
                  // drawing the bar if its width is greater than 1
                  if (barWidth > 1) {
                    g.fillRect(
                      x,
                      y,
                      barWidth,
                      (int) Math.round(m_histBarClassCount.valueSparse(j)
                        * heightRatio));
                  } else if ((m_histBarClassCount.valueSparse(j) * heightRatio) > 0) {
                    g.drawLine(
                      x,
                      y,
                      x,
                      (int) (y + Math.round(m_histBarClassCount.valueSparse(j)
                        * heightRatio)));
                  }
                  g.setColor(Color.black);
                  sum = sum + m_histBarClassCount.valueSparse(j);
                }
                // Drawing bar count on the top of the bar if it is < barWidth
                if (m_fm.stringWidth(" " + Utils.doubleToString(sum, 1)) < barWidth) {
                  g.drawString(" " + Utils.doubleToString(sum, 1), x, y - 1);
                }
                // Moving x to the next bar
                x = x + barWidth;
              }
            }

            // Now drawing the axis line at the bottom of the histogram
            // initializing x again to the start of the plot
            x = 3;
            if ((this.getWidth() - (x + m_histBarClassCounts.length * barWidth)) > 5) {
              x += (this.getWidth() - (x + m_histBarClassCounts.length
                * barWidth)) / 2;
            }

            g.drawLine(x, this.getHeight() - 17, (barWidth == 1) ? x + barWidth
              * m_histBarClassCounts.length - 1 : x + barWidth
              * m_histBarClassCounts.length, this.getHeight() - 17); // axis
                                                                     // line --
                                                                     // see
                                                                     // footnote
                                                                     // 2.
            g.drawLine(x, this.getHeight() - 16, x, this.getHeight() - 12); // minimum
                                                                            // line
            g.drawString(Utils.doubleToString(m_as.numericStats.min, 2), x,
              this.getHeight() - 12 + m_fm.getHeight()); // minimum value
            g.drawLine(x + (barWidth * m_histBarClassCounts.length) / 2,
              this.getHeight() - 16, x
                + (barWidth * m_histBarClassCounts.length) / 2,
              this.getHeight() - 12); // median line
            // Drawing median value. X position for drawing the value is: from
            // start of the plot take the mid point and subtract from it half
            // of the width of the value to draw.
            g.drawString(
              Utils.doubleToString(m_as.numericStats.max / 2
                + m_as.numericStats.min / 2, 2),
              x
                + (barWidth * m_histBarClassCounts.length)
                / 2
                - m_fm.stringWidth(Utils.doubleToString(m_as.numericStats.max
                  / 2 + m_as.numericStats.min / 2, 2)) / 2, this.getHeight()
                - 12 + m_fm.getHeight()); // median value
            g.drawLine((barWidth == 1) ? x + barWidth
              * m_histBarClassCounts.length - 1 : x + barWidth
              * m_histBarClassCounts.length, this.getHeight() - 16,
              (barWidth == 1) ? x + barWidth * m_histBarClassCounts.length - 1
                : x + barWidth * m_histBarClassCounts.length,
              this.getHeight() - 12); // maximum line
            g.drawString(
              Utils.doubleToString(m_as.numericStats.max, 2),
              (barWidth == 1) ? x
                + barWidth
                * m_histBarClassCounts.length
                - m_fm.stringWidth(Utils.doubleToString(m_as.numericStats.max,
                  2)) - 1 : x
                + barWidth
                * m_histBarClassCounts.length
                - m_fm.stringWidth(Utils.doubleToString(m_as.numericStats.max,
                  2)), this.getHeight() - 12 + m_fm.getHeight()); // maximum
                                                                  // value --
                                                                  // see 2.
          } else { // if class attribute is numeric
            // There is a padding of 3px on each side of the histogram.
            barWidth = ((this.getWidth() - 6) / m_histBarCounts.length) < 1 ? 1
              : ((this.getWidth() - 6) / m_histBarCounts.length);

            // Same as above. Pls inside of the if stmt.
            x = 3;
            if ((this.getWidth() - (x + m_histBarCounts.length * barWidth)) > 5) {
              x += (this.getWidth() - (x + m_histBarCounts.length * barWidth)) / 2;
            }

            // Same as above
            for (double m_histBarCount : m_histBarCounts) {
              // calculating the ration of the component's height compared to
              // the maxValue in our current attribute. Leaving 19 pixels to
              // draw the axis at the bottom of the histogram.
              heightRatio = (this.getHeight() - (float) m_fm.getHeight()
                - buttonHeight - 19)
                / m_maxValue;
              y = (int) (this.getHeight()
                - Math.round(m_histBarCount * heightRatio) - 19);
              // System.out.println("Filling x:"+x+" y:"+y+" width:"+barWidth+
              // " height:"+(m_histBarCounts[i]*heightRatio));
              // same as in the if stmt above
              if (barWidth > 1) {
                g.drawRect(x, y, barWidth,
                  (int) Math.round(m_histBarCount * heightRatio));
              } else if ((m_histBarCount * heightRatio) > 0) {
                g.drawLine(x, y, x,
                  (int) (y + Math.round(m_histBarCount * heightRatio)));
              }
              if (m_fm.stringWidth(" "
                + Utils.doubleToString(m_histBarCount, 1)) < barWidth) {
                g.drawString(" " + Utils.doubleToString(m_histBarCount, 1), x,
                  y - 1);
              }

              x = x + barWidth;
            }

            // Now drawing the axis at the bottom of the histogram
            x = 3;
            if ((this.getWidth() - (x + m_histBarCounts.length * barWidth)) > 5) {
              x += (this.getWidth() - (x + m_histBarCounts.length * barWidth)) / 2;
            }

            // This is exact the same as in the if stmt above. See the inside of
            // the stmt for details
            g.drawLine(x, this.getHeight() - 17, (barWidth == 1) ? x + barWidth
              * m_histBarCounts.length - 1 : x + barWidth
              * m_histBarCounts.length, this.getHeight() - 17); // axis line
            g.drawLine(x, this.getHeight() - 16, x, this.getHeight() - 12); // minimum
                                                                            // line
            g.drawString(Utils.doubleToString(m_as.numericStats.min, 2), x,
              this.getHeight() - 12 + m_fm.getHeight()); // minimum value
            g.drawLine(x + (barWidth * m_histBarCounts.length) / 2,
              this.getHeight() - 16, x + (barWidth * m_histBarCounts.length)
                / 2, this.getHeight() - 12); // median line
            g.drawString(
              Utils.doubleToString(m_as.numericStats.max / 2
                + m_as.numericStats.min / 2, 2),
              x
                + (barWidth * m_histBarCounts.length)
                / 2
                - m_fm.stringWidth(Utils.doubleToString(m_as.numericStats.max
                  / 2 + m_as.numericStats.min / 2, 2)) / 2, this.getHeight()
                - 12 + m_fm.getHeight()); // median value
            g.drawLine((barWidth == 1) ? x + barWidth * m_histBarCounts.length
              - 1 : x + barWidth * m_histBarCounts.length,
              this.getHeight() - 16, (barWidth == 1) ? x + barWidth
                * m_histBarCounts.length - 1 : x + barWidth
                * m_histBarCounts.length, this.getHeight() - 12); // maximum
                                                                  // line
            g.drawString(
              Utils.doubleToString(m_as.numericStats.max, 2),
              (barWidth == 1) ? x
                + barWidth
                * m_histBarCounts.length
                - m_fm.stringWidth(Utils.doubleToString(m_as.numericStats.max,
                  2)) - 1 : x
                + barWidth
                * m_histBarCounts.length
                - m_fm.stringWidth(Utils.doubleToString(m_as.numericStats.max,
                  2)), this.getHeight() - 12 + m_fm.getHeight()); // maximum
                                                                  // value
          }
          // System.out.println("barWidth:"+barWidth+
          // " histBarCount:"+m_histBarCounts.length);

        } else {
          g.clearRect(0, 0, this.getWidth(), this.getHeight());
          g.drawString(
            "Attribute is neither numeric nor nominal.",
            this.getWidth() / 2
              - m_fm.stringWidth("Attribute is neither numeric nor nominal.")
              / 2, this.getHeight() / 2 - m_fm.getHeight() / 2);
        }
      } // <--end if of calculation thread
      else if (m_displayCurrentAttribute) { // if still calculation thread is
                                            // running plot
        g.clearRect(0, 0, this.getWidth(), this.getHeight());
        g.drawString("Calculating. Please Wait...",
          this.getWidth() / 2 - m_fm.stringWidth("Calculating. Please Wait...")
            / 2, this.getHeight() / 2 - m_fm.getHeight() / 2);
      } else if (!m_displayCurrentAttribute) {
        g.clearRect(0, 0, this.getWidth(), this.getHeight());
        g.drawString("Too many values to display.",
          this.getWidth() / 2 - m_fm.stringWidth("Too many values to display.")
            / 2, this.getHeight() / 2 - m_fm.getHeight() / 2);
      }
    } // <--end if(m_as==null) this means
  }

  /**
   * Main method to test this class from command line
   * 
   * @param args The arff file and the index of the attribute to use
   */
  public static void main(String[] args) {
    if (args.length != 3) {
      final JFrame jf = new JFrame("AttribVisualization");
      AttributeVisualizationPanel ap = new AttributeVisualizationPanel();
      try {
        Instances ins = new Instances(new FileReader(args[0]));
        ap.setInstances(ins);
        System.out.println("Loaded: " + args[0] + "\nRelation: "
          + ap.m_data.relationName() + "\nAttributes: "
          + ap.m_data.numAttributes());
        ap.setAttribute(Integer.parseInt(args[1]));
      } catch (Exception ex) {
        ex.printStackTrace();
        System.exit(-1);
      }
      System.out.println("The attributes are: ");
      for (int i = 0; i < ap.m_data.numAttributes(); i++) {
        System.out.println(ap.m_data.attribute(i).name());
      }

      jf.setSize(500, 300);
      jf.getContentPane().setLayout(new BorderLayout());
      jf.getContentPane().add(ap, BorderLayout.CENTER);
      jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      jf.setVisible(true);
    } else {
      System.out.println("Usage: java AttributeVisualizationPanel"
        + " [arff file] [index of attribute]");
    }
  }
}

/*
 * t =(int) Math.ceil((float)(
 * (m_data.instance(k).value(m_attribIndex)-m_as.numericStats.min) / barRange));
 * 1. This equation gives a value between (i-1)+smallfraction and i if the
 * attribute m_attribIndex for the current instances lies in the ith interval.
 * We then increment the value of our i-1th field of our histogram/barplot
 * array. If, for example, barRange=3 then, apart from the 1st interval, each
 * interval i has values in the range (minValue+3*i-1, minValue+3*i]. The 1st
 * interval has range [minValue, minValue+i]. Hence it can be seen in the code
 * we specifically handle t=0 separately.
 */

/**
 * (barWidth==1)?x+barWidth*m_histBarClassCounts.length-1 :
 * x+barWidth*m_histBarClassCounts.length 2. In the case barWidth==1 we subtract
 * 1 otherwise the line become one pixel longer than the actual size of the
 * histogram
 */
